﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;

namespace UDS.Components
{
    public delegate void DBAction();

    /// <summary>
    /// 通用的数据库处理类，通过ado.net与数据库连接
    /// </summary>
    public class Database : IDisposable
    {
        private static string s_connectionString;
        private SqlConnection _sqlConnection;
        private SqlTransaction _sqlTransaction;

        /// <summary>
        /// The max number of attempts for executing a database operation.
        /// </summary>
        public const int RetryCount = 3;
        private static Random rnd = new Random();

        private static string ConnectionString
        {
            get
            {
                if (s_connectionString == null)
                {
                    string cs = ConfigurationManager.AppSettings["ConnectionString"];
                    if (string.IsNullOrEmpty(cs))
                    {
                        throw new InvalidOperationException("The ConnectionString have not been configured!");
                    }
                    s_connectionString = cs;
                }
                return s_connectionString;
            }
        }

        /// <summary>
        /// Indicates whether there is an active associated with the current connection?
        /// </summary>
        public bool IsInTransaction
        {
            get
            {
                return (_sqlTransaction != null) && (_sqlTransaction.Connection != null);
            }
        }

        #region Transaction

        public IDbTransaction BeginTransaction()
        {
            OpenDatabaseConnection();
            _sqlTransaction = _sqlConnection.BeginTransaction();

            return _sqlTransaction;
        }

        public void Commit()
        {
            _sqlTransaction.Commit();
            _sqlTransaction = null;
        }

        public void Rollback()
        {
            _sqlTransaction.Rollback();
            _sqlTransaction = null;
        }

        #endregion

        #region RunProc, RunDataReader

        /// <summary>
        /// 执行存储过程
        /// </summary>
        /// <param name="procName">存储过程的名称</param>
        /// <returns>返回存储过程返回值</returns>
        public int RunProc(string procName)
        {
            return RunProc(procName, null);
        }

        /// <summary>
        /// 执行存储过程
        /// </summary>
        /// <param name="procName">存储过程名称</param>
        /// <param name="parameters">存储过程所需参数</param>
        /// <returns>返回存储过程返回值</returns>
        public int RunProc(string procName, SqlParameter[] parameters)
        {
            try
            {
                SqlCommand cmd = CreateCommand(procName, parameters);
                cmd.ExecuteNonQuery();

                return (int)cmd.Parameters["ReturnValue"].Value;
            }
            finally
            {
                if (_sqlTransaction == null)
                {
                    Close();
                }
            }
        }

        /// <summary>
        /// Executes a stored procedure and returns a <see cref="SqlDataReader"/> to read the record set.
        /// </summary>
        public SqlDataReader RunDataReader(string procName)
        {
            return RunDataReader(procName, null);
        }

        /// <summary>
        /// Executes a stored procedure and returns a <see cref="SqlDataReader"/> to read the record set.
        /// </summary>
        public SqlDataReader RunDataReader(string procName, IEnumerable<SqlParameter> parameters)
        {
            SqlCommand cmd = CreateCommand(procName, parameters);
            if (_sqlTransaction == null)
            {
                return cmd.ExecuteReader(CommandBehavior.CloseConnection);
            }
            return cmd.ExecuteReader(CommandBehavior.Default);
        }

        /// <summary>
        /// Directly executes a sql script and returns a <see cref="SqlDataReader"/> to read the record set.
        /// </summary>
        public SqlDataReader RunSQLText(string sql, IEnumerable<SqlParameter> parameters)
        {
            SqlCommand cmd = CreateCommand(sql, parameters);
            cmd.CommandType = CommandType.Text;

            if (_sqlTransaction == null)
            {
                return cmd.ExecuteReader(CommandBehavior.CloseConnection);
            }
            return cmd.ExecuteReader(CommandBehavior.Default);
        }

        /// <summary>
        /// Directly executes a sql script and returns the return value of the stored procedure.
        /// </summary>
        public int RunSQLText2(string sql, IEnumerable<SqlParameter> parameters)
        {
            try
            {
                SqlCommand cmd = CreateCommand(sql, parameters);
                cmd.CommandType = CommandType.Text;
                cmd.ExecuteNonQuery();

                return (int)cmd.Parameters["ReturnValue"].Value;
            }
            finally
            {
                if (_sqlTransaction == null)
                {
                    Close();
                }
            }
        }

        #endregion RunProc

        #region IDisposable Members

        /// <summary>
        /// 释放资源
        /// </summary>
        public void Dispose()
        {
            Close();
        }

        #endregion

        #region MakeXXXParam

        /// <summary>
        /// 传入输入参数
        /// </summary>
        /// <param name="ParamName">存储过程名称</param>
        /// <param name="DbType">参数类型</param></param>
        /// <param name="Size">参数大小</param>
        /// <param name="Value">参数值</param>
        /// <returns>新的 parameter 对象</returns>
        public static SqlParameter MakeInParam(string ParamName, SqlDbType DbType, int Size, object Value)
        {
            return MakeParam(ParamName, DbType, Size, ParameterDirection.Input, Value);
        }

        /// <summary>
        /// 传入返回值参数
        /// </summary>
        /// <param name="ParamName">存储过程名称</param>
        /// <param name="DbType">参数类型</param>
        /// <param name="Size">参数大小</param>
        /// <returns>新的 parameter 对象</returns>
        public static SqlParameter MakeOutParam(string ParamName, SqlDbType DbType, int Size)
        {
            return MakeParam(ParamName, DbType, Size, ParameterDirection.Output, null);
        }

        /// <summary>
        /// 传入返回值参数
        /// </summary>
        /// <param name="ParamName">存储过程名称</param>
        /// <param name="DbType">参数类型</param>
        /// <param name="Size">参数大小</param>
        /// <returns>新的 parameter 对象</returns>
        public static SqlParameter MakeReturnParam(string ParamName, SqlDbType DbType, int Size)
        {
            return MakeParam(ParamName, DbType, Size, ParameterDirection.ReturnValue, null);
        }

        /// <summary>
        /// 生成存储过程参数
        /// </summary>
        /// <param name="ParamName">存储过程名称</param>
        /// <param name="DbType">参数类型</param>
        /// <param name="Size">参数大小</param>
        /// <param name="Direction">参数方向</param>
        /// <param name="Value">参数值</param>
        /// <returns>新的 parameter 对象</returns>
        public static SqlParameter MakeParam(string ParamName, SqlDbType DbType, Int32 Size, ParameterDirection Direction, object Value)
        {
            SqlParameter p = Size > 0 ? new SqlParameter(ParamName, DbType, Size) : new SqlParameter(ParamName, DbType);
            p.Direction = Direction;

            //if (!(Direction == ParameterDirection.Output && Value == null))
            //    p.Value = Value;
            if (Direction != ParameterDirection.Output && Direction != ParameterDirection.ReturnValue)
            {
                p.Value = (Value == null) ? DBNull.Value : Value;
            }

            return p;
        }

        #endregion MakeXXXParam

        /// <summary>
        /// 关闭数据库连接
        /// </summary>
        public void Close()
        {
            if (_sqlTransaction != null && _sqlTransaction.Connection != null)
            {
                // Automatically rolls back the current transaction.
                Rollback();
                _sqlTransaction = null;
            }

            if (_sqlConnection != null)
            {
                _sqlConnection.Close();
            }
        }

        /// <summary>
        /// 创建一个SqlCommand对象以此来执行存储过程
        /// </summary>
        /// <param name="procName">存储过程的名称</param>
        /// <param name="parameters">存储过程所需参数</param>
        /// <returns>返回SqlCommand对象</returns>
        private SqlCommand CreateCommand(string procName, IEnumerable<SqlParameter> parameters)
        {
            // Opens the database connection
            OpenDatabaseConnection();

            var cmd = new SqlCommand(procName, _sqlConnection);
            cmd.CommandType = CommandType.StoredProcedure;
            if (_sqlTransaction != null)
            {
                cmd.Transaction = _sqlTransaction;
            }

            // 依次把参数传入存储过程
            if (parameters != null)
            {
                foreach (SqlParameter parameter in parameters)
                {
                    cmd.Parameters.Add(parameter);
                }
            }

            // 加入返回参数
            cmd.Parameters.Add(new SqlParameter("ReturnValue", SqlDbType.Int, 4, ParameterDirection.ReturnValue, false, 0, 0, string.Empty, DataRowVersion.Default, null));
            return cmd;
        }

        private void OpenDatabaseConnection()
        {
            if (_sqlConnection == null)
            {
                _sqlConnection = new SqlConnection(ConnectionString);
            }
            if (_sqlConnection.State == ConnectionState.Closed)
            {
                _sqlConnection.Open();
            }
        }

        public static void RunWithRetry(DBAction dbAction)
        {
            int retry = RetryCount;
            while (true)
            {
                try
                {
                    // Perform the DB operation
                    dbAction();

                    // Jump out of the loop.
                    break;
                }
                catch (SqlException sqlEx)
                {
                    if ((--retry) > 0 && IsDeadLockException(sqlEx))
                    {
                        // Let's try again.
                        // Sleep for a while to allow the other command to complete
                        RandomSleep();
                        continue;
                    }

                    throw;
                }
            }
        }

        /// <summary>
        /// Thread Safe Random Sleep
        /// </summary>
        private static void RandomSleep()
        {                        // Sleep randomly to help avoid contention
            int random = 1;
            lock (rnd)
            {
                random = rnd.Next(1, 1000);
            }
            System.Threading.Thread.Sleep(random);
        }

        private static bool IsDeadLockException(SqlException ex)
        {
            if (ex.Number == 1205)
            {
                return true;
            }
            return false;
        }
    }
}